package app.species;

import java.util.*;
import java.io.*;
import java.lang.reflect.Array;
import java.lang.reflect.Field;

import sim.util.*;
import java.awt.Color;

public class ConfigParams implements Serializable {

	private static final long serialVersionUID = 1L;
	private Hashtable<String, String> table;
	private ArrayList<String> missingkeys = new ArrayList<String>();
	private String completeFile; // used when we want to write out the parameter
									// file
	private String fileName;

	public ConfigParams(String fileName) {

		this.fileName = fileName;
		BufferedReader br = null;
		String line;
		StringTokenizer tokenizer;
		String key, value;

		this.table = new Hashtable<String, String>();
		this.completeFile = new String();

		/*
		 * If there is an error opening the parameter file, print out an error
		 * and exit the constructor
		 */
		try {
			br = new BufferedReader(new FileReader(fileName));
		} catch (IOException e) {
			System.err.println("(ConfigParams.ConfigParams) ERROR: Could not open parameter file: " + fileName);
			return;
		}

		// read in the file
		try {

			/*
			 * Read in all of the parameters, and put the key/value pair in the
			 * hash table We will allow for comments in the parameter file -
			 * Comments in the parameter file will be ignored - Comments are
			 * lines that begin with a '#'
			 */
			while ((line = br.readLine()) != null) {

				/*
				 * This is where the contents of the parameter file is saved We
				 * can write out this string at a later time
				 */
				if (this.completeFile.equals("")) {
					this.completeFile = line;
				} else {
					this.completeFile += "\n" + line;
				}

				// skip over comments
				if (line.length() == 0 || line.charAt(0) == '#')
					continue;

				tokenizer = new StringTokenizer(line, ":");

				key = tokenizer.nextToken().trim().toUpperCase();
				value = tokenizer.nextToken().trim();

				this.table.put(key, value);

			}

		} catch (IOException e) {
			System.err
					.println("(ConfigParams.ConfigParams) ERROR: An IOException was thrown while reading " + fileName);
		}

	}
	
	public String createCompleteConfigurationFile(){
		String out = completeFile;
		for(String s: missingkeys){
			out += "\n"+s+": ";
		}
		return out;
	}

	public void configureClass(Class<?> c) {
		System.out.println("Configure: " + c);
		ArrayList<Field> fields = new ArrayList<Field>(10);
		Class<?> start = c;
		while (start.getSuperclass() != null) {
			Field[] entityFields = start.getDeclaredFields();

			for (int i = 0; i < entityFields.length; i++) {
				try {
					entityFields[i].setAccessible(true);
					entityFields[i].get(null);
					Parameter parameterField = entityFields[i].getAnnotation(Parameter.class);
					if (parameterField != null) {
						fields.add(entityFields[i]);
					}
				} catch (NullPointerException e) {
					// we are only interested in static fields, that will NOT
					// throw Nullpointerexceptions by
					// entityFields[i].get(null);
				} catch (IllegalArgumentException e) {
					// private elements will be ignored aswell.
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			start = start.getSuperclass();
		}
		for (Field f : fields) {
			Parameter p = f.getAnnotation(Parameter.class);
			String name = p.name();
			if ("".equals(name))
				name = f.getName();

			f.setAccessible(true);
			String out = "Configure Field: " + f.getName() + (!f.getName().equals(name)? (" as \"" + name):"" )+ "\" = ";
			try {
				if (f.getType().isPrimitive()) {
					// DOUBLE
					try {
						double type = (Double) f.get(null);
						System.out.println(out + getParam(name, type));
						f.set(null, getParam(name, type));
						continue;
					} catch (ClassCastException e) {
					}

					// INT
					try {
						int type = (Integer) f.get(null);
						System.out.println(out + getParam(name, type));
						f.set(null, getParam(name, type));
						continue;
					} catch (ClassCastException e) {
					}

					// BOOLEAN
					try {
						boolean type = (Boolean) f.get(null);
						System.out.println(out + getParam(name, type));
						f.set(null, getParam(name, type));
						continue;
					} catch (ClassCastException e) {
					}
					throw new RuntimeException("undefined primitve " + f);

				} else if (f.getType().isArray()) {

					// TODO: HOW TO INITIALIZE ARRAY TYPES?
					// f.getType().getConstructors()[0].newInstance();
					// f.set(null, getParam(name, f.getType().newInstance()));
					try {
						f.set(null, getParam(name, new boolean[0]));
						System.out.println(out + " array");
						continue;
					} catch (IllegalArgumentException e) {

					}
					try {
						f.set(null, getParam(name, new Color[0]));
						System.out.println(out + " array");
						continue;
					} catch (IllegalArgumentException e) {
					}
					try {
						f.set(null, getParam(name, new SpawnPoint[0]));
						System.out.println(out + " array");
						continue;
					} catch (IllegalArgumentException e) {
					}
					throw new RuntimeException("undefined array " + f);

				} else {

					try {
						String x = "";
						f.set(null, getParam(name, x));
						System.out.println(out + getParam(name, x));
						continue;
					} catch (IllegalArgumentException e) {

					}
					try {
						Double2D x = new Double2D();
						f.set(null, getParam(name, x));
						System.out.println(out + getParam(name, x));
						continue;
					} catch (IllegalArgumentException e) {

					}
					throw new RuntimeException("undefined object " + f);

				}

			} catch (NotDefinedException e) {

				// keep f at default value
				try {
					System.out.println(out + "NOT FOUND = " + f.get(null));
				} catch (Exception e1) {
					e.printStackTrace();
				}

			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	public void printParamsFile(String fileName) {

		PrintWriter printWriter;

		try {

			printWriter = new PrintWriter(new BufferedWriter(new FileWriter(fileName)));
			printWriter.println(this.completeFile);
			printWriter.close();

		} catch (IOException e) {
			System.err.println("(ConfigParams.printParamsFile) ERROR: An IOException was thrown while writing "
					+ fileName);
		}

	}

	private String getValue(String key) {
		if (this.table.containsKey(key))
			return table.get(key);
		missingkeys.add(key);
		throw new NotDefinedException(fileName + ": Key does not exist \"" + key + "\"");
	}

	public String getParam(String key, String l) {
		return getValue(key);
	}

	public <T> T getParam(String key, T t) {
		if (t != null)
			throw new RuntimeException("Type \"" + t.getClass().getName() + "\" is unknown for Configurator!");
		else
			throw new NullPointerException("Nullpointerexception for key: " + key);
	}

	public int getParam(String key, int i) {
		try {

			return Integer.parseInt(getValue(key));
		} catch (NumberFormatException e) {
			return 0;
		}
	}

	public long getParam(String key, long l) {
		try {
			return Long.parseLong(getValue(key));
		} catch (NumberFormatException e) {
			return 0;
		}
	}

	public double getParam(String key, double l) {
		try {
			return Double.parseDouble(getValue(key));
		} catch (NumberFormatException e) {
			return 0;
		}
	}

	public boolean getParam(String key, boolean l) {
		try {
			return Boolean.parseBoolean(getValue(key));
		} catch (NumberFormatException e) {
			return false;
		}
	}

	public Double2D getParam(String key, Double2D l) {

		StringTokenizer t = new StringTokenizer(getValue(key));
		double x, y;
		x = Double.parseDouble(t.nextToken());
		y = Double.parseDouble(t.nextToken());

		return new Double2D(x, y);
	}

	public int[] getParam(String key, int[] x) {

		StringTokenizer t = new StringTokenizer(getValue(key));
		int[] array = new int[t.countTokens()];
		for (int i = 0; i < array.length; i++) {
			array[i] = Integer.parseInt(t.nextToken());
		}
		return array;
	}

	public boolean[] getParam(String key, boolean[] x) {
		StringTokenizer t = new StringTokenizer(getValue(key));
		boolean[] array = new boolean[t.countTokens()];
		try {

			for (int i = 0; i < array.length; i++) {
				array[i] = t.nextToken().equalsIgnoreCase("true");
			}
			return array;

		} catch (Exception e) {

			System.err.println("(ConfigParams.getParamBooleanArray) ERROR: Value for " + key
					+ " is not of type boolean array");
			return null;

		}

	}

	public SpawnPoint[] getParam(String key, SpawnPoint[] x) {

		StringTokenizer t = new StringTokenizer(getValue(key));
		SpawnPoint[] array = new SpawnPoint[t.countTokens() / 4];
		try {

			for (int i = 0; i < array.length; i++) {
				array[i] = new SpawnPoint(Double.parseDouble(t.nextToken()), Double.parseDouble(t.nextToken()),
						Double.parseDouble(t.nextToken()), Double.parseDouble(t.nextToken()));
			}
			return array;

		} catch (Exception e) {

			System.err.println("(ConfigParams.getParamSpawnPointArray) ERROR: Value for " + key
					+ " is not of type SpawnPoint array");
			return null;

		}

	}

	public Color[] getParam(String key, Color[] x) {

		StringTokenizer t = new StringTokenizer(getValue(key));
		Color[] array = new Color[t.countTokens() / 3];
		try {

			for (int i = 0; i < array.length; i++) {
				array[i] = new Color(Float.parseFloat(t.nextToken()), Float.parseFloat(t.nextToken()),
						Float.parseFloat(t.nextToken()));
			}
			return array;

		} catch (Exception e) {

			System.err.println("(ConfigParams.getParamSpawnPointArray) ERROR: Value for " + key
					+ " is not of type SpawnPoint array");
			return null;

		}

	}
}
